#pragma once

#include <glew.h>
#include <GL\glfw.h>

#define _USE_MATH_DEFINES
#include <math.h>

// http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/gluperspective
inline
void glusPerspective( GLdouble fovy, GLdouble aspect, GLdouble nearDist, GLdouble farDist )
{
	GLdouble half_height = nearDist * tan( fovy * M_PI / 360.0 );
	GLdouble half_width = half_height * aspect;

	glFrustum( -half_width, half_width, -half_height, half_height, nearDist, farDist );
}
inline
float glusAmountf(const float vector[3])
{
	return sqrtf(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
}
inline
void glusNormalizef(float vector[3])
{
	unsigned int i;

	float a = glusAmountf(vector);

	if (a == 0.0f)
	{
		return;
	}

	for (i = 0; i < 3; i++)
	{
		vector[i] /= a;
	}
}
inline
float glusDotf(const float vector0[3], const float vector1[3])
{
	return vector0[0]*vector1[0] + vector0[1]*vector1[1] + vector0[2]*vector1[2];
}

inline
void glusCrossf(float result[3], const float vector0[3], const float vector1[3])
{
	unsigned int i;

	float	temp[3];

	temp[0] = vector0[1]*vector1[2] - vector0[2]*vector1[1];
	temp[1] = vector0[2]*vector1[0] - vector0[0]*vector1[2];
	temp[2] = vector0[0]*vector1[1] - vector0[1]*vector1[0];

	for (i = 0; i < 3; i++)
	{
		result[i] = temp[i];
	}
}

inline
void glusLoadIdentityf(float matrix[16])
{
	matrix[0] = 1.0f;
	matrix[1] = 0.0f;
	matrix[2] = 0.0f;
	matrix[3] = 0.0f;

	matrix[4] = 0.0f;
	matrix[5] = 1.0f;
	matrix[6] = 0.0f;
	matrix[7] = 0.0f;

	matrix[8] = 0.0f;
	matrix[9] = 0.0f;
	matrix[10] = 1.0f;
	matrix[11] = 0.0f;

	matrix[12] = 0.0f;
	matrix[13] = 0.0f;
	matrix[14] = 0.0f;
	matrix[15] = 1.0f;
}

inline
void glusMultMatrixf(float matrix[16], const float matrix0[16], const float matrix1[16])
{
	unsigned int i;
	unsigned int k; 

	float temp[16];

	for (i = 0; i < 16; i++)
	{
		temp[i] = 0.0f;

		for(k = 0; k < 4; k++)
		{
			//			  		row   column   		   row column
			temp[i] += matrix0[(i%4)+(k*4)] * matrix1[(k)+((i/4)*4)];
		}
	}

	for (i = 0; i < 16; i++)
	{
		matrix[i] = temp[i];
	}
}

inline
void glusTranslatef(float matrix[16], float x, float y, float z)
{
	float temp[16];

	glusLoadIdentityf(temp);

	temp[12] = x;
	temp[13] = y;
	temp[14] = z;

	glusMultMatrixf(matrix, matrix, temp);
}

inline
void glusLookAtf(float result[16], float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
{
	float forward[3], side[3], up[3];
	float matrix[16];

	forward[0] = centerX - eyeX;
	forward[1] = centerY - eyeY;
	forward[2] = centerZ - eyeZ;
	glusNormalizef(forward);

	up[0] = upX;
	up[1] = upY;
	up[2] = upZ;

	glusCrossf(side, forward, up);
	glusNormalizef(side);

	glusCrossf(up, side, forward);

	matrix[0] = side[0];
	matrix[1] = up[0];
	matrix[2] = -forward[0];
	matrix[3] = 0.0f;
	matrix[4] = side[1];
	matrix[5] = up[1];
	matrix[6] = -forward[1];
	matrix[7] = 0.0f;
	matrix[8] = side[2];
	matrix[9] = up[2];
	matrix[10] = -forward[2];
	matrix[11] = 0.0f;
	matrix[12] = 0.0f;
	matrix[13] = 0.0f;
	matrix[14] = 0.0f;
	matrix[15] = 1.0f;

	glusLoadIdentityf(result);
	glusMultMatrixf(result, result, matrix);

	glusTranslatef(result, -eyeX, -eyeY, -eyeZ);
}

// glusFrustum from nopper.tv
// opengl docs on how its made: http://www.opengl.org/sdk/docs/man/xhtml/glFrustum.xml
inline
void glusFrustumf(float result[16], float left, float right, float bottom, float top, float nearVal, float farVal)
{
	result[0] = 2.0f*nearVal/(right-left);
	result[1] = 0.0f;
	result[2] = 0.0f;
	result[3] = 0.0f;
	result[4] = 0.0f;
	result[5] = 2.0f*nearVal/(top-bottom);
	result[6] = 0.0f;
	result[7] = 0.0f;
	result[8] = (right+left)/(right-left);
	result[9] = (top+bottom)/(top-bottom);
	result[10] = -(farVal+nearVal)/(farVal-nearVal);
	result[11] = -1.0f;
	result[12] = 0.0f;
	result[13] = 0.0f;
	result[14] = -(2.0f*farVal*nearVal)/(farVal-nearVal);
	result[15] = 0.0f;
}

inline
void glusPerspectivef(float result[16], float fovy, float aspect, float zNear, float zFar)
{
	float xmin, xmax, ymin, ymax;

	ymax = zNear * tanf( fovy * float(M_PI) / 360.0f);
	ymin = -ymax;
	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glusFrustumf(result, xmin, xmax, ymin, ymax, zNear, zFar);
}

inline
bool glusIsRowZerof(const float matrix[16], unsigned int row)
{
	unsigned int column;

	for (column = 0; column < 4; column++)
	{
		if (matrix[column*4+row] != 0.0f)
			return false;
	}

	return true;
}

inline
void glusDevideRowByf(float result[16], float matrix[16], unsigned int row, float value)
{
	unsigned int column;

	for (column = 0; column < 4; column++)
	{
		matrix[column*4+row] /= value;
		result[column*4+row] /= value;
	}
}
	
inline
bool glusIsColumnZerof(const float matrix[16], unsigned int column)
{
	unsigned int row; 

	for (row = 0; row < 4; row++)
	{
		if (matrix[column*4+row] != 0.0f)
			return false;
	}
	
	return true;
}
	


inline
void glusAddRowf(float result[16], float matrix[16], unsigned int rowOne, unsigned int rowTwo, float factor)
{
	unsigned int column;

	for (column = 0; column < 4; column++)
	{
		matrix[column*4+rowOne] += matrix[column*4+rowTwo]*factor;
		result[column*4+rowOne] += result[column*4+rowTwo]*factor;
	}
}

inline
void glusSwapRowf(float result[16], float matrix[16], unsigned int rowOne, unsigned int rowTwo)
{
	unsigned int column;

	float temp;

	for (column = 0; column < 4; column++)
	{
		temp = matrix[column*4 + rowOne];
		matrix[column*4 + rowOne] = matrix[column*4 + rowTwo];
		matrix[column*4 + rowTwo] = temp;

		temp = result[column*4 + rowOne];
		result[column*4 + rowOne] = result[column*4 + rowTwo];
		result[column*4 + rowTwo] = temp;
	}	
}

bool glusInverseMatrixf(float matrix[16], const float source[16]);
